import { Suspense, lazy } from "react";

/* 检测数据类型 */
const class2type = {},
  toString = class2type.toString, //保存纯粹对象身上的toString方法
  hasOwn = class2type.hasOwnProperty; //该方法判断一个方法中是否存在某个私有属性

export const toType = function toType(obj) {
  // 因为这里需要匹配[]两个字符，因此需要转义
  // \w代表字母数字下划线
  // \W代表匹配任意非字母数字下划线
  // 两个组合一起，就是获取任意字符序列
  let reg = /^\[object ([\w\W]+)\]$/;
  if (obj == null) return obj + ""; //传啥返回啥,转换为字符串方便处理
  return typeof obj === "object" || typeof obj === "function"
    ? // 确保toString方法的上下文是obj，返回复杂数据类型（数组也属于对象）
      reg.exec(toString.call(obj))[1].toLowerCase()
    : typeof obj; //返回基本数据类型
};

export const isFunction = function isFunction(obj) {
  return (
    typeof obj === "function" &&
    typeof obj.nodeType !== "number" &&
    typeof obj.item !== "function"
  );
};

export const isWindow = function isWindow(obj) {
  return obj != null && obj === obj.window;
};

export const isPlainObject = function isPlainObject(obj) {
  let proto, Ctor;
  // 如果该属性不存在或者其转换后的类型不是对象直接返回false
  // 一定需要使用call方法修改this指向，否则第二个Object可能会是Undefined
  if (!obj || toString.call(obj) !== "[object Object]") return false;
  // 获取对象的原型
  proto = Object.getPrototypeOf(obj);
  // 该对象没有原型代表为纯粹对象
  if (!proto) return true;
  // 判断该原型是否存在构造函数
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  // 构造函数需要直接指向顶层Object函数,由谁创建的，构造器指向谁
  return typeof Ctor === "function" && Ctor === Object;
};
export const isArrayLike = function isArrayLike(obj) {
  // 该值必须存在，最后取length的值
  let length = !!obj && "length" in obj && obj.length,
    type = toType(obj); // 如果是数组，返回的[object Array]经过测试为array
  // 如果是一个函数 function(){}其属性上默认存在length属性0
  if (isFunction(obj) || isWindow(obj)) return false;
  // 因为这里只是判断是否为类数组，所以不需要使用&进行校验
  return (
    type === "array" ||
    length === 0 || //校验空数组情况
    (typeof length === "number" && length > 0 && length - 1 in obj) //最后一段代码判断其索引是否有效，常用于处理类数组
  );
};
export const each = function each(obj, callback) {
  // 判断是否为对象或者数组
  let isArray = isArrayLike(obj),
    isObject = isPlainObject(obj);
  if (!isArray && !isObject)
    throw new TypeError("obj must be a array or likeArray or plainObject");
  // 回调函数执行对应的操作
  if (!isFunction(callback)) throw new TypeError("callback is not a function");
  // 如果为数组，则执行对应的操作
  if (isArray) {
    for (let i = 0; i < obj.length; i++) {
      let item = obj[i], //元素值
        index = i; //元素下标
      // 如果回调函数中返回false则提前结束数组中的数据读取
      if (callback.call(item, item, index) === false) break;
    }
    return obj; //返回数据本身
  }
  // 如果为对象则执行对应的操作
  let keys = Object.getOwnPropertyNames(obj); //获取私有属性名（无法获取symbol）
  if (typeof Symbol !== "undefined")
    // 判断当前环境是否支持Symbol
    keys = keys.concat(Object.getOwnPropertySymbols(obj)); //获取私有Symbol属性名
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i], //键名
      value = obj[key]; //键值
    if (callback.call(value, value, key) === false) break;
  }
  return obj; //返回数据本身
};

export const clone = function clone(...params) {
  let target = params[0],
    deep = false,
    length = params.length,
    i = 1,
    isArray,
    isObject,
    result, //最终拷贝的结果
    treated; //存储处理过的数据，避免递归操作引起死循环
  if (typeof target === "boolean" && length > 1) {
    deep = target; // 标志位,是否深拷贝
    target = params[1]; //保存实际需要拷贝数据
    i = 2;
  }
  treated = params[i]; //获取已经处理完成的数据

  // 使用set结构去重，避免对同一个数据进行递归操作
  if (!treated) treated = new Set();
  // 如果是已经处理过的数据直接返回
  if (treated.has(target)) return target;

  //treated，深拷贝的情况下，传入的数据依次取出重新放入set中
  // 浅拷贝和深拷贝treated中存储的数据不一样
  treated.add(target);

  // 校验传递进来的数据是数组还是普通对象
  isArray = Array.isArray(target);
  isObject = isPlainObject(target);

  // 处理null或undefined的情况
  if (target == null) return target;

  // 两个判断排除特殊情况
  // 如果不是数组，不是普通对象，不是函数类型却又是普通对象
  if (
    !isArray &&
    !isObject &&
    !isFunction(target) &&
    typeof target === "object"
  ) {
    try {
      // 将target的值复制到一个新的对象中
      return new target.constructor(target);
    } catch (_) {
      return target;
    }
  }
  // 普通数据的情况下直接返回，如number/string
  if (!isArray && !isObject) {
    return target;
  }

  // 创建新的对象引用
  /* 这行代码的目的是为了创建一个与 target 具有相同构造函数的新对象，从而保留了对象的类型信息 */
  result = new target.constructor();
  // 遍历对象或数组中的每一个元素进行拷贝
  each(target, (copy, name) => {
    if (deep) {
      // 深拷贝
      result[name] = clone(deep, copy, treated);
      return;
    }
    result[name] = copy; //浅拷贝
  });
  return result;
};

export const SuspenseComponent = (com) => {
  const Comp = lazy(() => import(`../views${com}`));
  return (
    <Suspense fallback={<></>}>
      <Comp />
    </Suspense>
  );
};

export function convertImgUrl(imgURL) {
  //去掉一些奇怪的符号。。。。
  const SERVER_URL = process.env.REACT_APP_SERVER_URL;
  if (!imgURL) return "http://dummyimage.com/400x400";
  if (imgURL.startsWith("http")) return imgURL;
  return SERVER_URL + `/${imgURL}`;
}
